#include <cmath>
#include <sstream>
#include <stack>
#include <chrono>
#include <fstream>
#include <iomanip>

#include <filesystem>

#include "AABB.h"
#include "SparseGrid.h"
#include "stuff.h"
#include "CloudJS.hpp"
#include "PointAttributes.hpp"
#include "PointReader.h"
#include "LASPointReader.h"
#include "PotreeException.h"

#include "PotreeReader.h"

namespace Potree {

	PRNode::PRNode(PotreeReader* potreeReader, AABB aabb) {
		this->potreeReader = potreeReader;
		this->aabb = aabb;
		this->grid = new SparseGrid(aabb, spacing());
	}

	PRNode::PRNode(PotreeReader* potreeReader, int index, AABB aabb, int level) {
		this->index = index;
		this->aabb = aabb;
		this->level = level;
		this->potreeReader = potreeReader;
		this->grid = new SparseGrid(aabb, spacing());
	}

	PRNode::~PRNode() {
		for (PRNode* child : children) {
			if (child != NULL) {
				delete child;
			}
		}
		delete grid;
	}

	string PRNode::name() const {
		if (parent == NULL) {
			return "r";
		}
		else {
			return parent->name() + std::to_string(index);
		}
	}

	float PRNode::spacing() {
		return float(potreeReader->spacing / pow(2.0, float(level)));
	}

	string PRNode::workDir() {
		return potreeReader->workDir;
	}

	string PRNode::hierarchyPath() {
		string path = "r/";

		int hierarchyStepSize = potreeReader->hierarchyStepSize;
		string indices = name().substr(1);

		int numParts = (int)floor((float)indices.size() / (float)hierarchyStepSize);
		for (int i = 0; i < numParts; i++) {
			path += indices.substr(i * hierarchyStepSize, hierarchyStepSize) + "/";
		}

		return path;
	}

	string PRNode::path() {
		string path = hierarchyPath() + name() + potreeReader->getExtension();
		return path;
	}

	PointReader* PRNode::createReader(string path) {
		PointReader* reader = NULL;
		OutputFormat outputFormat = this->potreeReader->outputFormat;
		if (outputFormat == OutputFormat::LAS || outputFormat == OutputFormat::LAZ) {
			reader = new LASPointReader(path);
		}

		return reader;
	}

	void PRNode::loadFromDisk() {

		PointReader* reader = createReader(workDir() + "/data/" + path());
		while (reader->readNextPoint()) {
			Point p = reader->getPoint();

			if (isLeafNode()) {
				store.push_back(p);
			}
			else {
				grid->addWithoutCheck(p);
			}
		}
		grid->numAccepted = numAccepted;
		reader->close();
		delete reader;

		isInMemory = true;
	}

	PRNode* PRNode::createChild(int childIndex) {
		AABB cAABB = childAABB(aabb, childIndex);
		PRNode* child = new PRNode(potreeReader, childIndex, cAABB, level + 1);
		child->parent = this;
		children[childIndex] = child;

		return child;
	}

	PRNode* PRNode::findNode(string name) {
		string thisName = this->name();

		if (name.size() == thisName.size()) {
			return (name == thisName) ? this : NULL;
		}
		else if (name.size() > thisName.size()) {
			int childIndex = stoi(string(1, name[thisName.size()]));
			if (!isLeafNode() && children[childIndex] != NULL) {
				return children[childIndex]->findNode(name);
			}
			else {
				return NULL;
			}
		}
		else {
			return NULL;
		}
	}

	void PRNode::loadNode(PRNode* node)
	{
		node->loadFromDisk();
		for (int i = 0; i < node->children.size(); i++)
		{
			if (node->children[i])
			{
				PRNode::loadNode(node->children[i]);
			}
		}
	}

	long long PRNode::checkPointCount(PRNode* node)
	{
		long long ret = node->getPointCount();
		for (int i = 0; i < node->children.size(); i++)
		{
			if (node->children[i])
			{
				ret += PRNode::checkPointCount(node->children[i]);
			}
		}
		return ret;
	}

	PotreeReader::PotreeReader(string workDir, ConversionQuality quality) {
		this->workDir = workDir;
		this->quality = quality;
	}

	PotreeReader::PotreeReader(string workDir, AABB aabb, float spacing, int maxDepth, double scale, OutputFormat outputFormat, PointAttributes pointAttributes, ConversionQuality quality) {
		this->workDir = workDir;
		this->aabb = aabb;
		this->spacing = spacing;
		this->scale = scale;
		this->maxDepth = maxDepth;
		this->outputFormat = outputFormat;
		this->quality = quality;

		this->pointAttributes = pointAttributes;

		if (this->scale == 0) {
			if (aabb.size.length() > 1'000'000) {
				this->scale = 0.01;
			}
			else if (aabb.size.length() > 100'000) {
				this->scale = 0.001;
			}
			else if (aabb.size.length() > 1) {
				this->scale = 0.001;
			}
			else {
				this->scale = 0.0001;
			}
		}

		cloudjs.outputFormat = outputFormat;
		cloudjs.boundingBox = aabb;
		cloudjs.octreeDir = "data";
		cloudjs.spacing = spacing;
		cloudjs.version = "1.8";
		cloudjs.scale = this->scale;
		cloudjs.pointAttributes = pointAttributes;

		root = new PRNode(this, aabb);
	}

	string PotreeReader::getExtension() {
		if (outputFormat == OutputFormat::LAS) {
			return ".las";
		}
		else if (outputFormat == OutputFormat::LAZ) {
			return ".laz";
		}
		else if (outputFormat == OutputFormat::BINARY) {
			return ".bin";
		}

		return "";
	}

	void PotreeReader::loadStateFromDisk() {


		{// cloudjs
			string cloudJSPath = workDir + "/cloud.js";
			ifstream file(cloudJSPath);
			string line;
			string content;
			while (std::getline(file, line)) {
				content += line + "\n";
			}
			cloudjs = CloudJS(content);
		}

		{
			this->outputFormat = cloudjs.outputFormat;
			this->pointAttributes = cloudjs.pointAttributes;
			this->hierarchyStepSize = cloudjs.hierarchyStepSize;
			this->spacing = cloudjs.spacing;
			this->scale = cloudjs.scale;
			this->aabb = cloudjs.boundingBox;
			this->numAccepted = cloudjs.numAccepted;

		}

		{// tree
			vector<string> hrcPaths;
			fs::path rootDir(workDir + "/data/r");
			for (fs::recursive_directory_iterator iter(rootDir), end; iter != end; ++iter) {
				fs::path path = iter->path();
				if (fs::is_regular_file(path)) {
					if (iEndsWith(path.extension().string(), ".hrc")) {
						hrcPaths.push_back(path.string());
					}
					else {

					}
				}
				else if (fs::is_directory(path)) {

				}
			}
			std::sort(hrcPaths.begin(), hrcPaths.end(), [](string& a, string& b) {
				return a.size() < b.size();
			});

			PRNode* root = new PRNode(this, cloudjs.boundingBox);
			for (string hrcPath : hrcPaths) {

				fs::path pHrcPath(hrcPath);
				string hrcName = pHrcPath.stem().string();
				PRNode* hrcRoot = root->findNode(hrcName);

				PRNode* current = hrcRoot;
				current->addedSinceLastFlush = false;
				current->isInMemory = false;
				vector<PRNode*> nodes;
				nodes.push_back(hrcRoot);

				ifstream fin(hrcPath, ios::in | ios::binary);
				std::vector<char> buffer((std::istreambuf_iterator<char>(fin)), (std::istreambuf_iterator<char>()));

				for (int i = 0; 5 * i < (int)buffer.size(); i++) {
					PRNode* current = nodes[i];

					char children = buffer[i * 5];
					char* p = &buffer[i * 5 + 1];
					unsigned int* ip = reinterpret_cast<unsigned int*>(p);
					unsigned int numPoints = *ip;

					//std::bitset<8> bs(children);
					//cout << i << "\t: " << "children: " << bs << "; " << "numPoints: " << numPoints << endl;

					current->numAccepted = numPoints;

					if (children != 0) {
						current->children.resize(8, NULL);
						for (int j = 0; j < 8; j++) {
							if ((children & (1 << j)) != 0) {
								AABB cAABB = childAABB(current->aabb, j);
								PRNode* child = new PRNode(this, j, cAABB, current->level + 1);
								child->parent = current;
								child->addedSinceLastFlush = false;
								child->isInMemory = false;
								current->children[j] = child;
								nodes.push_back(child);
							}
						}
					}

				}
			}

			this->root = root;

			// TODO set it to actual number
			this->numAdded = 1;

			//int numNodes = 0;
			//root->traverse([&](PRNode *node){
			//	if(numNodes < 50){
			//		cout << std::left << std::setw(10) << node->name();
			//		cout << std::right << std::setw(10) << node->numAccepted << "; ";
			//		cout << node->aabb.min << " - " << node->aabb.max << endl;
			//	}
			//
			//	numNodes++;
			//
			//});
		}


	}

	void PotreeReader::load()
	{
		PRNode::loadNode(root);
	}

	long long PotreeReader::checkPointCount()
	{
		return PRNode::checkPointCount(root);
	}
}